using System;
using System.IO;
#if GODOT
using Godot;
#endif

namespace Common {
	public sealed class File : IDisposable {
#if GODOT
		static readonly Godot.File staticFile = new Godot.File();
#endif

		public static bool IsExists(string path) {
#if STANDARD
			return System.IO.File.Exists(path);
#endif
#if GODOT
			return staticFile.FileExists(path);
#endif
		}
		public static string GetMD5(string path) {
#if STANDARD
			throw new PlatformNotSupportedException("File.GetMD5 does not work on this platform!");
#endif
#if GODOT
			return staticFile.GetMd5(path);
#endif
		}
		public static string GetSHA256(string path) {
#if STANDARD
			throw new PlatformNotSupportedException("File.GetSHA256 does not work on this platform!");
#endif
#if GODOT
			return staticFile.GetSha256(path);
#endif
		}
		public static void Delete(string path) {
			if (IsExists(path)) {
#if STANDARD
				System.IO.File.Delete(path);
#endif
#if GODOT
				Directory.Delete(path);
#endif
			}
		}
		public enum OpenMode {
			/// <summary>
			/// Open the file for reading.
			/// </summary>
#if STANDARD
			Read = 0,
#endif
#if GODOT
			Read = Godot.File.ModeFlags.Read,
#endif
			/// <summary>
			/// Truncate and open the file for writing.
			/// </summary>
#if STANDARD
			TruncateWrite = 1,
#endif
#if GODOT
			TruncateWrite = Godot.File.ModeFlags.Write,
#endif
			/// <summary>
			/// Open the file for both reading and writing.
			/// </summary>
#if STANDARD
			ReadWrite = 2,
#endif
#if GODOT
			ReadWrite = Godot.File.ModeFlags.ReadWrite,
#endif
			/// <summary>
			/// Truncate and open the file for both reading and writing.
			/// </summary>
#if STANDARD
			TruncateReadWrite = 3
#endif
#if GODOT
			TruncateReadWrite = Godot.File.ModeFlags.WriteRead
#endif

		}

		/// <summary>
		/// Open the file at the given path in the given mode.
		/// </summary>
		/// <param name="path">The path of the file to open</param>
		/// <param name="mode">The open mode that decides avaiable operations</param>
		/// <returns>The operator of the opened file.</returns>
		/// <exception cref="FileNotFoundException">The target file is not found.</exception>
		/// <exception cref="UnauthorizedAccessException">The program lacks the required permissions for opening the file -OR- the file is in use.</exception>
		/// <exception cref="DirectoryNotFoundException">Bad drive -OR- bad path.</exception>
		/// <exception cref="IOException">Cannot open the target file.</exception>
		/// <exception cref="Exception"><see cref="System.IO.File.Open(string, FileMode, FileAccess)">Other exceptions.</see></exception>
		public static File Open(string path, OpenMode mode) {
#if STANDARD
			ValueTuple<FileMode, FileAccess> modeTuple = mode switch {
				OpenMode.Read => (FileMode.Open, FileAccess.Read),
				OpenMode.TruncateWrite => (FileMode.Create, FileAccess.Write),
				OpenMode.ReadWrite => (FileMode.Append, FileAccess.ReadWrite),
				OpenMode.TruncateReadWrite => (FileMode.Create, FileAccess.ReadWrite),
				_ => (FileMode.Create, FileAccess.ReadWrite)
			};
			var file = System.IO.File.Open(path, modeTuple.Item1, modeTuple.Item2);
			return new File(file);

#endif
#if GODOT
			var file = new Godot.File();
			var err = file.Open(path, (Godot.File.ModeFlags)mode);
			switch (err) {
				case Error.FileNotFound:
					throw new FileNotFoundException(path);
				case Error.FileNoPermission:
					throw new UnauthorizedAccessException("No permission for opening: " + path);
				case Error.FileAlreadyInUse:
					throw new UnauthorizedAccessException("File is already in use: " + path);
				case Error.FileBadDrive:
					throw new DirectoryNotFoundException("Bad drive: " + path);
				case Error.FileBadPath:
					throw new DirectoryNotFoundException("Bad path: " + path);
				case Error.FileCantOpen:
					throw new IOException("Cannot open: " + path);
			}
			return new File(file);
#endif
		}

#if STANDARD
		readonly FileStream sourceFile = null;
		readonly StreamReader streamReader = null;
		readonly StreamWriter streamWriter = null;

		File(FileStream source) {
			sourceFile = source;
			streamReader = new StreamReader(source);
			streamWriter = new StreamWriter(source);
		}
#endif
#if GODOT
		readonly Godot.File sourceFile = null;

		File(Godot.File source) {
			sourceFile = source;
		}
#endif


		/// <summary>
		/// The length of the opened file in bytes.
		/// </summary>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public long Length {
			get {
				if (disposedValue) {
					throw new ObjectDisposedException(this.ToString());
				}
#if STANDARD
				return sourceFile.Length;
#endif
#if GODOT
				return sourceFile.GetLen();
#endif
			}
		}

		/// <summary>
		/// The path of the opened file.
		/// </summary>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public string Path {
			get {
				if (disposedValue) {
					throw new ObjectDisposedException(this.ToString());
				}
#if STANDARD
				throw new PlatformNotSupportedException("File.Path only works on GodotSharp!");
#endif
#if GODOT
				return sourceFile.GetPath();
#endif
			}
		}

		/// <summary>
		/// The cursor position of the opened file.
		/// </summary>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public long Position {
			get {
				if (disposedValue) {
					throw new ObjectDisposedException(this.ToString());
				}
#if STANDARD
				return sourceFile.Position;
#endif
#if GODOT
				return sourceFile.GetPosition();
#endif

			}
			set {
				if (disposedValue) {
					throw new ObjectDisposedException(this.ToString());
				}
#if STANDARD
				sourceFile.Position = value;
#endif
#if GODOT
				sourceFile.Seek(value);
#endif

			}
		}

		/// <summary>
		/// Get all text from the opened file.
		/// </summary>
		/// <returns>All the content of the opened file as text.</returns>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public string GetAllText() {
#if STANDARD
			Position = 0;
			return streamReader.ReadToEnd();
#endif
#if GODOT
			return sourceFile.GetAsText();
#endif
		}

		/// <summary>
		/// Write text into the opened file.
		/// </summary>
		/// <param name="text">The text to write.</param>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public void WriteText(string text) {
			if (disposedValue) {
				throw new ObjectDisposedException(this.ToString());
			}
#if STANDARD
			streamWriter.Write(text);
#endif
#if GODOT
			sourceFile.StoreString(text);
#endif
		}

		/// <summary>
		/// Read one line of text from the opened file.
		/// </summary>
		/// <returns>One line of text read.</returns>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public string ReadLine() {
			if (disposedValue) {
				throw new ObjectDisposedException(this.ToString());
			}
#if STANDARD
			return streamReader.ReadLine() ?? string.Empty;
#endif
#if GODOT
			return sourceFile.GetLine();
#endif
		}

		/// <summary>
		/// Write one line of text into the opened file.
		/// </summary>
		/// <param name="text">The text to write.</param>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public void WriteLine(string text) {
			if (disposedValue) {
				throw new ObjectDisposedException(this.ToString());
			}
#if STANDARD
			streamWriter.WriteLine(text);
#endif
#if GODOT
			sourceFile.StoreLine(text);
#endif
		}

		/// <summary>
		/// Read bytes of the given length from the opened file.
		/// </summary>
		/// <param name="length">The amount of data to read in bytes.</param>
		/// <returns>The read bytes.</returns>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public byte[] ReadBytes(int length) {
			if (disposedValue) {
				throw new ObjectDisposedException(this.ToString());
			}
#if STANDARD
			byte[] result = new byte[length];
			sourceFile.Read(result, 0, length);
			return result;
#endif
#if GODOT
			return sourceFile.GetBuffer(length);
#endif
		}

		/// <summary>
		/// Read all bytes from the opened file.
		/// </summary>
		/// <returns>The read bytes.</returns>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		/// <exception cref="NotSupportedException">The length of the file is larger than the max value of int.</exception>
		public byte[] ReadAllBytes() {
			if (Length > int.MaxValue) {
				throw new NotSupportedException("The length of the file is larger than the max value of int.");
			}
			Position = 0;
			return ReadBytes((int)Length);
		}

		/// <summary>
		/// Write bytes into the opened file.
		/// </summary>
		/// <param name="buffer">The bytes to write.</param>
		/// <exception cref="ObjectDisposedException">The file has been closed.</exception>
		public void WriteBytes(byte[] buffer) {
			if (disposedValue) {
				throw new ObjectDisposedException(this.ToString());
			}
#if STANDARD
			sourceFile.Write(buffer, 0, buffer.Length);
#endif
#if GODOT
			sourceFile.StoreBuffer(buffer);
#endif
		}

		/// <summary>
		/// Close the opened file.
		/// It is the same as Dispose();
		/// </summary>
		public void Close() => Dispose();

		#region IDisposable interface. DO NOT EDIT.
		private bool disposedValue;
		private void Dispose(bool disposing) {
			if (!disposedValue) {
#if STANDARD
				sourceFile.Close();
				streamReader.Close();
				streamWriter.Close();
#endif
#if GODOT
				sourceFile.Close();
#endif
				disposedValue = true;
			}
		}
		~File() {
			Dispose(false);
		}

		/// <summary>
		/// Close the opened file.
		/// </summary>
		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		#endregion
	}
}